// @Author EthanScriptOn
// @Desc
package proxy

import (
	"errors"
	"fmt"
	"gitee.com/fatzeng/srf_switch_basic_components/proxy/interceptor"
	"gitee.com/fatzeng/srf_switch_basic_components/tool"
	"gitee.com/fatzeng/srf_switch_basic_components/util"
	"reflect"
)

// Proxy Proxy object, proxy the target, do something before the agent with the interceptor,
// and do something after the agent
type Proxy struct {
	methodDispatcher *tool.MethodDispatcher
	optionsMapping   map[string]*options
}

// options Actionable options before and after the agent
type options struct {
	targetMethodIndex  int
	interceptorOptions []interceptor.SrfSwitchInterceptorOptions
	errorCollection    *tool.ErrorCollection
}

// GetTarget Gets the object being proxied
func (p *Proxy) GetTarget() interface{} {
	return p.methodDispatcher.GetTarget()
}

// GenerateProxy proxy objects based on a type
func GenerateProxy(tp reflect.Type) (*Proxy, error) {
	proxyInstance, err := tool.GetBuilder(tp).Build()
	if err != nil {
		return nil, err
	}
	return build(proxyInstance)
}

// TargetProxy Proxy target object
func TargetProxy(tg interface{}) (*Proxy, error) {
	return build(tg)
}

// build Build a proxy object proxy
func build(target interface{}) (*Proxy, error) {
	instance, err := tool.GenerateMethodDispatcher(target)
	if err != nil {
		return nil, err
	}
	return &Proxy{
		methodDispatcher: instance,
		optionsMapping:   make(map[string]*options),
	}, nil
}

// Call The core logic of the proxy, which uses methodDispatcher to complete the pre- and post-placement of the proxy logic
// The error in the return value is the error thrown by the objective function
func (p *Proxy) Call(methodInter interface{}, args ...interface{}) (methodCall *tool.MethodCall, err error) {
	defer tool.HandleErr(&err)
	methodName := util.GetMethodNameWithMethod(methodInter)
	if methodName == "" {
		return nil, errors.New("unable to obtain the method name")
	}
	optionsList, _ := p.optionsMapping[methodName]
	p.wrapperCall(func() bool {
		if methodCall, err = p.methodDispatcher.Call(methodName, args...); err != nil {
			return false
		}
		return true
	}, optionsList)
	return
}

// wrapperCall The core logic, where the proxy logic is executed
func (p *Proxy) wrapperCall(targetMethodCall func() bool, options *options) {
	// There is no interceptor option to execute the proxy function directly
	if options == nil || len(options.interceptorOptions) <= 0 {
		targetMethodCall()
		return
	}
	interceptorOptionsLength := len(options.interceptorOptions)
	for index := 0; index < interceptorOptionsLength; index++ {
		// The execution of the function to be proxyed
		if options.targetMethodIndex == index && !targetMethodCall() {
			return
		}
		interceptorOption := options.interceptorOptions[index]
		// Check if the interceptor option can be executed
		if ipt, err := interceptorOption.IsPassThrough(); ipt {
			// Execute the interceptor option
			interceptorOption.Call()
			// Error handling for interceptor options
			if err = interceptorOption.GetError(); err != nil {
				interceptorOption.OnError(err)
			}
		} else {
			// There may be reasons why execution is not allowed
			if err != nil {
				interceptorOption.OnError(err)
			}
		}
	}
}

// Before Executes before the proxy logic
func (o *options) Before(interceptorOptions interceptor.SrfSwitchInterceptorOptions) *options {
	var err error
	defer o.errorCollection.WithErrorCollector(err)
	if len(o.interceptorOptions) == 0 {
		o.interceptorOptions = append(o.interceptorOptions, interceptorOptions)
	} else {
		if newInterceptorOptions, err := util.InsertAtFirst(o.interceptorOptions, interceptorOptions); err != nil {
			err = fmt.Errorf("unable to insert the interceptor options err: [%v]", err)
			return o
		} else {
			o.interceptorOptions = newInterceptorOptions.([]interceptor.SrfSwitchInterceptorOptions)
		}
	}
	o.targetMethodIndex++
	return o
}

// After Executes after the proxy logic
func (o *options) After(interceptorOptions interceptor.SrfSwitchInterceptorOptions) *options {
	o.interceptorOptions = append(o.interceptorOptions, interceptorOptions)
	return o
}

// GetError An error occurred in the build once action item
func (o *options) GetError() error {
	return o.errorCollection.GetError()
}

// Use Use this function before executing, methodInter is the object function to be proxied,
// and methodInter must be defined in the proxy object
func (p *Proxy) Use(methodInter interface{}) (opts *options) {
	opts = &options{
		interceptorOptions: make([]interceptor.SrfSwitchInterceptorOptions, 0),
		errorCollection:    new(tool.ErrorCollection),
	}
	var err error
	defer opts.errorCollection.WithErrorCollector(err)
	methodName := util.GetMethodNameWithMethod(methodInter)
	if methodName == "" {
		err = errors.New("unable to obtain the method name")
		return
	}
	p.optionsMapping[methodName] = opts
	return
}
